package com.speedcar.base.aspect;

import com.google.common.util.concurrent.RateLimiter;
import com.speedcar.base.annotation.RateLimit;
import com.speedcar.base.exception.ErrorCode;
import com.speedcar.base.exception.SpeedCarCommonException;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Aspect
@Component
@Slf4j
public class ScRateLimitAspect {

    private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();

    // SpEL表达式解析器
    private final ExpressionParser parser = new SpelExpressionParser();
    // 参数名发现器
    private final LocalVariableTableParameterNameDiscoverer discoverer =
            new LocalVariableTableParameterNameDiscoverer();


    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
        String key = generateKey(point, rateLimit);
        RateLimiter limiter = limiters.computeIfAbsent(key, k -> RateLimiter.create(rateLimit.permitsPerSecond()));

        if (limiter.tryAcquire()) {
            return point.proceed();
        } else {
            throw new SpeedCarCommonException(ErrorCode.RATE_LIMIT_ERROR);
        }
    }

    private String generateKey(ProceedingJoinPoint point, RateLimit rateLimit) {
        String keyExpression = rateLimit.key();
        String url = "";
        HttpServletRequest request;
        if (Objects.nonNull(RequestContextHolder.getRequestAttributes())) {
            request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            url = url + request.getRequestURI();
        }
        if (StringUtils.isEmpty(keyExpression)) {
            // 如果没有指定key，则使用方法签名作为默认key
            return url + ":" + point.getSignature().toLongString();
        }

        // 解析SpEL表达式
        try {
            // 获取方法参数
            Object[] args = point.getArgs();
            val signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();

            // 获取参数名
            String[] paramNames = discoverer.getParameterNames(method);
            if (paramNames == null || args == null || paramNames.length != args.length) {
                return url + ":" + keyExpression; // 无法解析，返回原始表达式
            }

            // 创建表达式解析上下文
            EvaluationContext context = new StandardEvaluationContext();
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }

            // 解析表达式
            Expression expression = parser.parseExpression(keyExpression);
            Object result = expression.getValue(context);

            // 如果解析结果不为空，使用解析结果作为key
            if (result != null) {
                return url + ":" + result;
            }
        } catch (Exception e) {
            log.warn("解析限流key表达式失败: {}, 使用默认key", keyExpression, e);
        }

        // 解析失败时使用表达式本身作为key
        return url + ":" + keyExpression;
    }
}
